package ns

import (
	"GLibHac/fssystem"
	"GLibHac/tools"
	"fmt"
)

type KernelAccessControl struct {
	Capabilities []int32
}

func NewKernelAccessControl(stream fssystem.IStorage, offset, size int32) (r *KernelAccessControl, err error) {
	err = stream.SetPosition(int64(offset))
	if err != nil {
		return nil, err
	}
	r = &KernelAccessControl{}
	r.Capabilities = make([]int32, size)

	for index := 0; index < len(r.Capabilities); index++ {
		r.Capabilities[index], err = stream.ReadInt()
		if err != nil {
			return nil, err
		}
	}
	return
}

type ServiceAccessControl struct {
	Services map[string]bool
}

func NewServiceAccessControl(stream fssystem.IStorage, offset, size int32) (r *ServiceAccessControl, err error) {
	err = stream.SetPosition(int64(offset))
	if err != nil {
		return nil, err
	}

	bytesRead := int32(0)

	for bytesRead != size {
		controlByte, err := stream.ReadByte()
		if err != nil {
			return nil, err
		}

		if controlByte == 0 {
			break
		}

		length := int32(controlByte&0x07) + 1
		registerAllowed := (controlByte & 0x80) != 0
		bytes, err := stream.ReadBytes(int(length))
		if err != nil {
			return nil, err
		}
		r.Services[string(bytes)] = registerAllowed
		bytesRead += length + 1
	}
	return
}

type FsAccessHeader struct {
	Version                   int32
	PermissionsBitmask        uint64
	DataAndContentOwnerIdSize int32
	ContentOwnerIdSize        int32
}

func NewFsAccessHeader(stream fssystem.IStorage, offset, size int32) (r *FsAccessHeader, err error) {
	r = &FsAccessHeader{}
	err = stream.SetPosition(int64(offset))
	if err != nil {
		return nil, err
	}
	r.Version, err = stream.ReadInt()
	r.PermissionsBitmask, err = stream.ReadUint64()

	dataSize, err := stream.ReadInt()

	if dataSize != 0x1c {
		return nil, fmt.Errorf("fsAccessHeader is corrupted")
	}

	r.ContentOwnerIdSize, err = stream.ReadInt()
	r.DataAndContentOwnerIdSize, err = stream.ReadInt()

	if r.DataAndContentOwnerIdSize != 0x1c {
		return nil, fmt.Errorf("contentOwnerId section is not implemented")
	}
	return
}

const Aci0Magic = 'A' | 'C'<<8 | 'I'<<16 | '0'<<24

type Aci0 struct {
	TitleId              uint64
	FsVersion            int32
	FsPermissionsBitmask uint64
	ServiceAccessControl *ServiceAccessControl
	KernelAccessControl  *KernelAccessControl
}

func NewAci0(stream fssystem.IStorage, offset int32) (r *Aci0, err error) {
	r = &Aci0{}
	err = stream.SetPosition(int64(offset))
	if err != nil {
		return nil, err
	}
	readInt, err := stream.ReadInt()
	if readInt != Aci0Magic {
		return nil, fmt.Errorf("ACI0 Stream doesn't contain ACI0 section")
	}

	err = stream.SetPosition(stream.Position() + 0xc)
	if err != nil {
		return
	}

	r.TitleId, err = stream.ReadUint64()
	if err != nil {
		return nil, err
	}
	// Reserved.
	err = stream.SetPosition(stream.Position() + 8)
	if err != nil {
		return nil, err
	}

	fsAccessHeaderOffset, err := stream.ReadInt()
	if err != nil {
		return nil, err
	}
	fsAccessHeaderSize, err := stream.ReadInt()
	if err != nil {
		return nil, err
	}
	serviceAccessControlOffset, err := stream.ReadInt()
	if err != nil {
		return nil, err
	}
	serviceAccessControlSize, err := stream.ReadInt()
	if err != nil {
		return nil, err
	}
	kernelAccessControlOffset, err := stream.ReadInt()
	if err != nil {
		return nil, err
	}
	kernelAccessControlSize, err := stream.ReadInt()
	if err != nil {
		return nil, err
	}

	fsAccessHeader, err := NewFsAccessHeader(stream, offset+fsAccessHeaderOffset, fsAccessHeaderSize)

	r.FsVersion = fsAccessHeader.Version
	r.FsPermissionsBitmask = fsAccessHeader.PermissionsBitmask

	r.ServiceAccessControl, err = NewServiceAccessControl(stream, offset+serviceAccessControlOffset, serviceAccessControlSize)
	if err != nil {
		return nil, err
	}
	r.KernelAccessControl, err = NewKernelAccessControl(stream, offset+kernelAccessControlOffset, kernelAccessControlSize)
	if err != nil {
		return nil, err
	}
	return
}

type Acid struct{}

func NewAcid(reader fssystem.IStorage, offset int32) (*Acid, error) {
	return &Acid{}, nil
}

const NpdmMagic = 'M' | 'E'<<8 | 'T'<<16 | 'A'<<24

type Npdm struct {
	ProcessFlags        byte
	Is64Bit             bool
	MainThreadPriority  byte
	DefaultCpuId        byte
	PersonalMmHeapSize  int32
	Version             int32
	MainThreadStackSize int32
	TitleName           string
	ProductCode         []byte
	*Aci0
	*Acid
	Aci0Size int32
	AcidSize int32
}

func NewNpdm(reader fssystem.IStorage) (r *Npdm, err error) {
	readInt, err := reader.ReadInt()
	if err != nil {
		return nil, err
	}
	if readInt != NpdmMagic {
		return nil, fmt.Errorf("NPDM Stream doesn't contain NPDM file")
	}

	_, err = reader.ReadBytes(4)
	if err != nil {
		return nil, err
	}

	r.ProcessFlags, err = reader.ReadByte()
	if err != nil {
		return nil, err
	}
	r.Is64Bit = (r.ProcessFlags & 1) != 0

	if _, err = reader.ReadByte(); err != nil {
		return nil, err
	}

	r.MainThreadPriority, err = reader.ReadByte()
	if err != nil {
		return nil, err
	}
	r.DefaultCpuId, err = reader.ReadByte()
	if err != nil {
		return nil, err
	}
	if _, err = reader.ReadBytes(4); err != nil {
		return nil, err
	}

	r.PersonalMmHeapSize, err = reader.ReadInt()
	if err != nil {
		return nil, err
	}
	r.Version, err = reader.ReadInt()
	if err != nil {
		return nil, err
	}
	r.MainThreadStackSize, err = reader.ReadInt()
	if err != nil {
		return nil, err
	}

	tempTitleName, err := reader.ReadBytes(0x10)
	if err != nil {
		return nil, err
	}

	r.TitleName = tools.Utf8ZToString(tempTitleName)

	r.ProductCode, err = reader.ReadBytes(0x10)

	err = reader.SetPosition(reader.Position() + 0x30)
	if err != nil {
		return nil, err
	}
	aci0Offset, err := reader.ReadInt()
	if err != nil {
		return nil, err
	}
	r.Aci0Size, err = reader.ReadInt()
	if err != nil {
		return nil, err
	}
	acidOffset, err := reader.ReadInt()
	if err != nil {
		return nil, err
	}
	r.AcidSize, err = reader.ReadInt()
	if err != nil {
		return nil, err
	}

	r.Aci0, err = NewAci0(reader, aci0Offset)
	r.Acid, err = NewAcid(reader, acidOffset)
	return
}
